#pragma once
#include "match.hpp"
#include "session.hpp"
#include <functional>
#define WWWROOT "./wwwroot"
class gobang_server
{
private:
    std::string web_root; // 静态资源根目录
    websocket_server _wsserver;
    user_table _user_table;
    online_manager _online_manager;
    room_manager _room_manager;
    matcher _matcher;
    session_manager _session_manager;

private:
    session_ptr get_session_by_cookie(websocket_server::connection_ptr &conn)
    {
        auto req = conn->get_request();
        std::string cookie_str = req.get_header("Cookie");
        if (cookie_str.empty())
        {
            DEBUG_LOG("没有找到cookie信息");
            ws_resp(conn,"hall_ready",false,"没有找到cookie信息,需要重新登录");
            return session_ptr();
        }
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);
        if (ret == false)
        {
            DEBUG_LOG("没有找到session信息");
            ws_resp(conn,"hall_ready",false,"没有找到SSID信息,需要重新登录");
            return session_ptr();
        }
        session_ptr ss_ptr = _session_manager.get_session_by_ssid(std::stol(ssid_str));
        if (ss_ptr.get() == nullptr)
        {
            DEBUG_LOG("session信息过期");
            ws_resp(conn,"hall_ready",false,"session信息过期,需要重新登录");
            return session_ptr();
        }
        return ss_ptr;
    }
    // 静态资源请求
    void FileHandle(websocket_server::connection_ptr &conn)
    {
        websocketpp::http::parser::request req = conn->get_request();
        // std::string method=req.get_method();
        std::string uri = req.get_uri();
        std::string path_name = web_root + uri;
        // 如果打开文件是个目录，则在末尾加上login.html
        if (path_name.back() == '/')
            path_name += "login.html";
        DEBUG_LOG("path:%s", path_name.c_str());
        std::string body;
        bool ret = file_util::read(path_name, body);
        Json::Value resp;
        if (ret == false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset='UTF-8'/>";
            body += "</head>";
            body += "<body>";
            body += "<h1>Not Found</h1>";
            body += "</body>";
            conn->set_status(websocketpp::http::status_code::value::not_found);
            return;
        }
        // 服务器默认响应500，所以需要手动设置ok状态码
        conn->set_status(websocketpp::http::status_code::value::ok);
        conn->set_body(body);
    }
    void http_resp(websocket_server::connection_ptr conn,
                   bool result, const std::string &reason,
                   websocketpp::http::status_code::value rep_val)
    {
        Json::Value resp;
        resp["result"] = result;
        resp["reason"] = reason;
        std::string resp_body;
        json_util::Serialize(resp, resp_body);
        conn->set_status(rep_val); 
        conn->append_header("Content-Type", "application/json");
        conn->set_body(resp_body);
    }
    // 注册请求
    void Reg(websocket_server::connection_ptr conn)
    {
        auto req = conn->get_request();
        std::string req_body = req.get_body();
        Json::Value login_info;
        Json::Value resp;
        bool ret = json_util::UnSerialize(login_info, req_body);
        if (ret == false)
            return http_resp(conn, false, "请求正文格式错误", websocketpp::http::status_code::value::bad_request);
        if (login_info["username"].asString().empty() || login_info["password"].asString().empty())
            return http_resp(conn, false, "请输入用户名与密码", websocketpp::http::status_code::value::bad_request);
        ret = _user_table.insert(login_info);
        if (ret == false)
            return http_resp(conn, false, "用户名已经被占用", websocketpp::http::status_code::value::bad_request);
        // 成功了返回200
        return http_resp(conn, true, "注册用户成功", websocketpp::http::status_code::value::ok);
    }
    // 登录请求
    void Login(websocket_server::connection_ptr conn)
    {
        auto req = conn->get_request();
        std::string req_body = req.get_body();
        Json::Value login_info;
        Json::Value resp;
        bool ret = json_util::UnSerialize(login_info, req_body);
        if (ret == false)
        {
            DEBUG_LOG("反序列化登录信息失败");
            return http_resp(conn, false, "请求正文格式错误", websocketpp::http::status_code::value::bad_request);
        }
        if (login_info["username"].asString().empty()|| login_info["password"].asString().empty())
        {
            DEBUG_LOG("用户名或密码为空");
            return http_resp(conn, false, "请输入用户名与密码", websocketpp::http::status_code::value::bad_request);
        }
        if (_user_table.login(login_info) == false)
        {
            DEBUG_LOG("用户名或密码错误");
            return http_resp(conn, false, "用户名或密码错误", websocketpp::http::status_code::value::bad_request);
        }
        int uid = login_info["id"].asUInt64();
        if(_online_manager.is_game_hall(uid)||_online_manager.is_game_room(uid))
        {
            DEBUG_LOG("二次登录");
            return http_resp(conn, false, "二次登录", websocketpp::http::status_code::value::bad_request);
        }
        auto ssp = _session_manager.create_session(uid, LOGIN);
        if (ssp.get() == nullptr)
        {
            DEBUG_LOG("创建会话失败");
            return http_resp(conn, false, "服务器内部错误", websocketpp::http::status_code::value::internal_server_error);
        }
        uint64_t ssid = ssp->get_ssid();
        _session_manager.set_session_expire_time(ssid, SESSION_TIMEOUT);
        std::string cookie_ssid = "SSID=" + std::to_string(ssid);
        conn->append_header("Set-Cookie", cookie_ssid);
        return http_resp(conn, true, "登录成功", websocketpp::http::status_code::value::ok);
    }
    bool get_cookie_val(std::string &cookie_str, const std::string &key, std::string &val)
    {
        // Cookie: SSID=XXX; path=XXX; ...
        // 以"; "作为分割，寻找cookie信息
        std::vector<std::string> string_vec;
        string_util::split(cookie_str, ": ", string_vec);
        // 寻找以key为键的cookie信息
        for (auto &str : string_vec)
        {
            std::vector<std::string> cookie_vec;
            string_util::split(str, "=", cookie_vec);
            if (cookie_vec.size() != 2)
                continue;
            if (cookie_vec[0] == key)
            {
                val = cookie_vec[1];
                return true;
            }
        }
        return false;
    }
    // 用户信息获取
    void Info(websocket_server::connection_ptr conn)
    {
        session_ptr ss_ptr = get_session_by_cookie(conn);
        if (ss_ptr.get() == nullptr)
            return;
        uint64_t uid = ss_ptr->get_user();
        // 从数据库中取出用户信息
        Json::Value user_info;
        bool ret = _user_table.select_by_id(uid, user_info);
        if (ret == false)
        {
            DEBUG_LOG("找不到用户信息");
            return http_resp(conn, false, "找不到用户信息,请重新登录", websocketpp::http::status_code::value::bad_request);
        }
        std::string resp_body;
        json_util::Serialize(user_info, resp_body);
        conn->set_status(websocketpp::http::status_code::value::ok);
        conn->set_body(resp_body);
        conn->append_header("Content-Type", "application/json");
        _session_manager.set_session_expire_time(ss_ptr->get_ssid(), SESSION_TIMEOUT);
    }
    void HttpHandle(websocketpp::connection_hdl hdl)
    {
        websocket_server::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        auto req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        if (method == "POST" && uri == "/reg")
            Reg(conn);
        else if (method == "POST" && uri == "/login")
            Login(conn);
        else if (method == "GET" && uri == "/info")
            Info(conn);
        else
            FileHandle(conn);
    }
    void ws_resp(websocket_server::connection_ptr conn,const std::string& opt,
                bool result,const std::string& reason)
    {
        Json::Value resp;
        if(!opt.empty()) resp["optype"] = opt;
        resp["result"] = result;
        if(!reason.empty()) resp["reason"] = reason;
        std::string resp_body;
        json_util::Serialize(resp, resp_body);
        conn->send(resp_body);
    }
    void open_game_hall(websocket_server::connection_ptr conn)
    {
        // 1.登陆验证-是否成功登录
        session_ptr ss_ptr = get_session_by_cookie(conn);
        if (ss_ptr.get() == nullptr)
            return;
        // 2.判断客户端是否是二次登录
        uint64_t uid=ss_ptr->get_user();
        if(_online_manager.is_game_hall(uid)||_online_manager.is_game_room(uid))
        {
            DEBUG_LOG("二次登录");
            return ws_resp(conn,"hall_ready",false,"二次登录");
        }
        // 3.将当前客户端与连接加入游戏大厅&游戏房间在线用户管理
        _online_manager.enter_game_hall(uid,conn);
        // 4.给客户端响应游戏大厅建立成功
        Json::Value resp;
        resp["optype"]="hall_ready";
        resp["result"] = true;
        std::string resp_body;
        json_util::Serialize(resp, resp_body);
        conn->send(resp_body);
        // 5.将session设置为永久存在
        _session_manager.set_session_expire_time(ss_ptr->get_ssid(),SESSION_FOREVER);
    }
    void open_game_room(websocket_server::connection_ptr conn)
    {
        //获取用户session
        session_ptr ss_ptr = get_session_by_cookie(conn);
        if (ss_ptr.get() == nullptr)
            return;
        //判断用户是否已经处在在线用户管理的游戏大厅或者游戏房间中--在线用户管理
        uint64_t uid=ss_ptr->get_user();
        if(_online_manager.is_game_hall(uid)||_online_manager.is_game_room(uid))
        {
            DEBUG_LOG("二次登录");
            return ws_resp(conn,"room_ready",false,"二次登录");
        }
        //判断当前用户是否已经创建好了房间
        room_ptr rp=_room_manager.get_room_by_user_id(uid);
        if(rp.get()==nullptr)
        {
            DEBUG_LOG("没有找到玩家房间信息");
            return ws_resp(conn,"room_ready",false,"没有找到玩家房间信息");
        }
        //将用户添加到游戏房间中
        _online_manager.enter_game_room(uid,conn);
        //将用户回话设置为永久连接
        _session_manager.set_session_expire_time(ss_ptr->get_ssid(),SESSION_FOREVER);
        //6.回复房间准备完毕
        Json::Value json_room;
        json_room["optype"]="room_ready";
        json_room["result"]=true;
        json_room["room_id"]=rp->Id();
        json_room["uid"]=uid;
        json_room["white_id"]=rp->Get_White_Id();
        json_room["black_id"]=rp->Get_Black_Id();
        std::string resp;
        json_util::Serialize(json_room,resp);
        //DEBUG_LOG("%s",resp.c_str());
        conn->send(resp);
    }
    void OpenHandle(websocketpp::connection_hdl hdl)
    {
        // 长连接建立成功之后的回调函数
        websocket_server::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        auto req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            // 建立了游戏大厅的长连接
            open_game_hall(conn);
        }
        else if (uri == "/room")
        {
            // 建立了游戏房间的长连接
            open_game_room(conn);
        }
    }
    void ws_close_game_hall(websocket_server::connection_ptr conn)
    {
        DEBUG_LOG("玩家退出游戏大厅");
        session_ptr ss_ptr = get_session_by_cookie(conn);
        if (ss_ptr.get() == nullptr)
            return;
        //将玩家从游戏大厅中移除
        _online_manager.exit_game_hall(ss_ptr->get_user());
        //恢复session生命周期，设置定时销毁
        _session_manager.set_session_expire_time(ss_ptr->get_ssid(),SESSION_TIMEOUT);
    }
    void ws_close_game_room(websocket_server::connection_ptr conn)
    {
        DEBUG_LOG("玩家退出游戏房间");
        session_ptr ss_ptr = get_session_by_cookie(conn);
        if (ss_ptr.get() == nullptr)
            return;
        uint64_t ssid=ss_ptr->get_ssid();
        uint64_t uid=ss_ptr->get_user();
        _session_manager.set_session_expire_time(ssid,SESSION_TIMEOUT);
        _online_manager.exit_game_room(uid);
        _room_manager.remove_room_user(uid);
    }
    void CloseHandle(websocketpp::connection_hdl hdl)
    {
        websocket_server::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        auto req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            // 建立了游戏大厅的长连接
            ws_close_game_hall(conn);
        }
        else if (uri == "/room")
        {
            // 建立了游戏房间的长连接
            ws_close_game_room(conn);
        }
    }
    void ws_msg_game_hall(websocket_server::connection_ptr conn,websocket_server::message_ptr msg)
    {
        //身份验证
        session_ptr ss_ptr = get_session_by_cookie(conn);
        if(ss_ptr.get()==nullptr)
            return;
        //获取请求信息
        std::string str=msg->get_payload();
        Json::Value req;
        bool ret=json_util::UnSerialize(req,str);
        if(ret==false)
        {
            DEBUG_LOG("请求解析出错");
            return ws_resp(conn,"",false,"请求信息解析出错");
        }
        //两种请求，开始对战匹配/停止对战匹配
        //1.请求类型不为空
        if(!req["optype"].isNull())
        {
            //类型为对战匹配
            if(req["optype"]=="match_start")
            {
                _matcher.add(ss_ptr->get_user());
                return ws_resp(conn,"match_start",true,"");
            }
            //类型为取消对战匹配
            else if(req["optype"]=="match_stop")
            {
                _matcher.del(ss_ptr->get_user());
                return ws_resp(conn,"match_stop",true,"");
            }
        }
        return ws_resp(conn,"unknow",false,"");
    }
    void ws_msg_game_room(websocket_server::connection_ptr conn,websocket_server::message_ptr msg)
    {
        session_ptr ss_ptr = get_session_by_cookie(conn);
        if(ss_ptr.get()==nullptr)
            return;
        room_ptr rp=_room_manager.get_room_by_user_id(ss_ptr->get_user());
        if(rp.get()==nullptr)
        {
            DEBUG_LOG("没有找到玩家房间信息");
            return ws_resp(conn,"unknow",false,"没有找到玩家房间信息");
        }
        Json::Value req;
        std::string str=msg->get_payload();
        bool ret=json_util::UnSerialize(req,str);
        if(ret==false)
        {
            return ws_resp(conn,"unknow",false,"请求解析失败");
        }
        rp->Hanle_Request(req);
    }
    void MessageHandle(websocketpp::connection_hdl hdl, websocket_server::message_ptr msg)
    {
        websocket_server::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        auto req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            // 建立了游戏大厅的长连接
            ws_msg_game_hall(conn,msg);
        }
        else if (uri == "/room")
        {
            // 建立了游戏房间的长连接
            ws_msg_game_room(conn,msg);
        }
    }

public:
    // 进行成员初始化以及回调函数的设置
    gobang_server(
        const std::string &wwwroot,
        const std::string &host, const std::string &username,
        const std::string &passwd, const std::string &dbname, uint16_t port = 3306)
        : web_root(wwwroot),
          _user_table(host, username, passwd, dbname, port),
          _room_manager(&_online_manager, &_user_table),
          _matcher(&_user_table, &_room_manager, &_online_manager),
          _session_manager(&_wsserver)
    {
        DEBUG_LOG("服务器开始创建");
        _wsserver.set_access_channels(websocketpp::log::alevel::none);
        // 初始化asio调度器
        _wsserver.init_asio();
        _wsserver.set_reuse_addr(true); // 设置可重用地址
        // 设置回调函数
        _wsserver.set_http_handler(std::bind(&gobang_server::HttpHandle, this, std::placeholders::_1));
        _wsserver.set_open_handler(std::bind(&gobang_server::OpenHandle, this, std::placeholders::_1));
        _wsserver.set_close_handler(std::bind(&gobang_server::CloseHandle, this, std::placeholders::_1));
        _wsserver.set_message_handler(std::bind(&gobang_server::MessageHandle, this, std::placeholders::_1, std::placeholders::_2));
    }
    void start(int port)
    {
        _wsserver.listen(port);
        _wsserver.start_accept();
        // 启动服务器
        _wsserver.run(); // 阻塞接口
    }
};